﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Xml;
using System.Xml.Schema;
using Xtee.CodeDom.Wrapper;
using Xtee.Core;
using Xtee.Core.Schema;

namespace Xtee.CodeDom.MetaInfo
{
    public class ElementInfo : InfoBase
    {

        private bool? _isAnonymous;
        public override bool IsAnonymous
        {
            get
            {
                if (Element.IsAnonymous())
                {
                    if (Element.IsSimpleType())
                        return (_isAnonymous = true).Value;

                    if (Element.IsComplexType() && Element.ElementSchemaType != null && Element.ElementSchemaType.IsArray())
                    {
                        if (Element.SchemaType.IsArray() && IsArrayElementAnonymos((XmlSchemaComplexType)Element.SchemaType, Resolver))
                            return (_isAnonymous = true).Value;

                        return (_isAnonymous = false).Value;
                    }

                    if (Element.IsComplexType())
                        return (_isAnonymous = true).Value;

                }

                else if (Element.ElementSchemaType.IsArray() && IsArrayElementAnonymos((XmlSchemaComplexType)Element.ElementSchemaType, Resolver))
                    return (_isAnonymous = true).Value;

                return (_isAnonymous = false).Value;
            }
        }

        public override bool IsNullable => Element.IsNillable;
        public override bool IsArray => ArrayItemElementInfo != null;

        public override bool IsRequired
        {
            get
            {
                if (OccurenceInfo.MinOccurs == 0)
                    return false;
                
                return !IsNullable;
            }
        }
        private bool? _isSpecialArray;
        public bool IsSpecialArray => _isSpecialArray ?? (_isSpecialArray = GetIsSpecialArray(Element)).Value;
        private ElementInfo _arrayItemElementInfo;
        public ElementInfo ArrayItemElementInfo => _arrayItemElementInfo ?? (_arrayItemElementInfo = GetArrayItemElementInfo());
        
        private string _arrayFullTypeName;

        public string ArrayFullTypeName
        {
            get
            {
                if (_arrayFullTypeName != null)
                    return _arrayFullTypeName;

                if (!IsArray)
                    return _arrayFullTypeName = "";

                return _arrayFullTypeName ??
                       (_arrayFullTypeName =
                           string.Format("System.Collections.Generic.List<{0}>",
                               !string.IsNullOrEmpty(ArrayItemElementInfo.SystemEquivalentFullTypeName)
                                   ? ArrayItemElementInfo.SystemEquivalentFullTypeName
                                   : ArrayItemElementInfo.FullTypeName));

            }
        }
        
        public XmlSchemaElement Element { get; set; }
        private XmlSchema _schema;
        public XmlSchema Schema => _schema?? (_schema = GetElementsXmlSchema(Element, Element));
        public override XmlQualifiedName QualifiedName => IsAnonymous ? new XmlQualifiedName(Element.QualifiedName.Name, Schema.TargetNamespace) : Element.QualifiedName;
        public override XmlQualifiedName SchemaTypeName => Element.SchemaTypeName;
        
        private string _elementPath;
        public override string ElementPath => _elementPath??(_elementPath=XmlSchemaComplexTypeWrapper.GetElementPath(Element, null, null));
        
        private TypeInfo _elementTypeInfo;

        public override TypeInfo TypeInfo
        {
            get
            {
                if (_elementTypeInfo != null)
                    return _elementTypeInfo;

                var xmlSchemaComplexType = Element.ElementSchemaType as XmlSchemaComplexType;
                if (xmlSchemaComplexType != null)
                    return _elementTypeInfo = new ComplexTypeInfo(FullName, xmlSchemaComplexType, Owner);

                return _elementTypeInfo = new SimpleTypeInfo(FullName, (XmlSchemaSimpleType) Element.ElementSchemaType, Owner);
            }
        }

        private OccurenceInfo _occurenceInfo;
        public OccurenceInfo OccurenceInfo =>_occurenceInfo??(_occurenceInfo = new OccurenceInfo(Element.MinOccurs));
        private bool _noAttachments;
        private AttachmentInfo _attachmentInfo;
        
        public AttachmentInfo AttachmentInfo
        {
            get
            {
                if (_noAttachments)
                    return null;
                
                if (_attachmentInfo != null)
                    return _attachmentInfo;

                if (Element.ElementSchemaType.QualifiedName == new XmlQualifiedName("swaRef", XmlSchemaTypeExtensions.SwaRefSchema))
                    return _attachmentInfo = new SwaRefAttachmentInfo();

                if (Element.ElementSchemaType.QualifiedName == new XmlQualifiedName("base64Binary", XmlSchemaTypeExtensions.XMLSCHEMA) && Element.UnhandledAttributes != null)
                {
                    var attachmentAttribute = Element.UnhandledAttributes.ToList().First(x => x.LocalName == "expectedContentTypes" && x.NamespaceURI == XmlSchemaTypeExtensions.XmlMimeSchema);

                    if (attachmentAttribute != null)
                        return _attachmentInfo = new MtomAttachmentInfo(attachmentAttribute.Value);

                }
                _noAttachments = true;
                return null;
            }
        }

        private string _name;
        public override string Name
        {
            get
            {
                if (_name != null)
                    return _name;

                _name = Element.QualifiedName.Name.Pascalize();

                if (Owner != null && Owner.NameInfo.Name == Name) _name = _name + "0";

                return _name;
            }
        }
        
        private string _fullName;
        public sealed override string FullName
        {
            get
            {
                if (_fullName != null)
                    return _fullName;
                
                if (Element.IsAnonymous() && !Element.IsArray())
                {
                    if (Element.IsSimpleType())
                    {
                        return _fullName = TryToResolveAnonymousTypeName(Element);
                    }

                    if (Element.IsComplexType())
                    {
                        if (Element.SchemaType.IsArray())
                        {
                            var wr = new XmlSchemaArrayItemTypeWrapper((XmlSchemaComplexType)Element.SchemaType, Resolver);
                            
                            if (ArrayItemElementInfo?.AttachmentInfo != null)
                                return _fullName = typeof(MultipartFile).FullName;

                            var xmlSchemaElement = wr.GetArrayElement();
                            XmlQualifiedName referencedTypeName = xmlSchemaElement.SchemaTypeName;
                            
                            if (!referencedTypeName.IsEmpty)
                            {
                                string propertyFullTypeName = new EntityClassNameInfo(referencedTypeName, false, Resolver).GlobalName;
                                if (referencedTypeName.HasBuiltInType())
                                {
                                    propertyFullTypeName = TypeMappings.GetXsdType(referencedTypeName.Name, referencedTypeName.Namespace).FullName;
                                }

                                return _fullName = propertyFullTypeName;
                            }

                            return Element.Name.Pascalize() + "Type";

                        }

                        return TryToResolveAnonymousTypeName(Element);
                    }
                }

                else if (Element.ElementSchemaType != null && Element.ElementSchemaType is XmlSchemaComplexType && Element.ElementSchemaType.IsArray())
                {
                    if (ArrayItemElementInfo?.AttachmentInfo != null)
                        return _fullName = typeof(MultipartFile).FullName;

                    var xmlSchemaElement = new XmlSchemaArrayItemTypeWrapper((XmlSchemaComplexType)Element.ElementSchemaType, Owner.Resolver).GetArrayElement();

                    if (xmlSchemaElement.IsAnonymous())
                        return _fullName = TryToResolveAnonymousTypeName(xmlSchemaElement);

                    return new EntityClassNameInfo(xmlSchemaElement.SchemaTypeName, false, Resolver).GlobalName;
                }

                else if (AttachmentInfo != null)
                    return _fullName = typeof(MultipartFile).FullName;

                if (Element.SchemaTypeName.HasBuiltInType())
                {
                    return _fullName = TypeMappings.GetXsdType(Element.SchemaTypeName.Name, Element.SchemaTypeName.Namespace).FullName;
                }


                return _fullName = new EntityClassNameInfo(Element.SchemaTypeName, false, Resolver).GlobalName;
            }
        }

        public ElementInfo GetArrayItemElementInfo()
        {
            if (Element.IsComplexType() && Element.ElementSchemaType != null && Element.ElementSchemaType.IsSoapEncArray())
                throw new NonGeneratableTypeException("SOAP-ENC:ARRAY not supported in document/literal style.");

            bool isSp = GetIsSpecialArray(Element);
            if (isSp)
                return this;
            
            if (Element.IsAnonymous())
            {
                if (Element.IsComplexType() && Element.SchemaType.IsArray())
                {
                    XmlSchemaElement arrayElement =
                        new XmlSchemaArrayItemTypeWrapper((XmlSchemaComplexType)Element.ElementSchemaType, Resolver).
                            GetArrayElement();

                    return new ElementInfo(arrayElement, Owner);
                }
            }


            else
            if (Element.ElementSchemaType != null && Element.ElementSchemaType is XmlSchemaComplexType &&
                     Element.ElementSchemaType.IsArray())
            {
                XmlSchemaElement arrayElement =
                    new XmlSchemaArrayItemTypeWrapper((XmlSchemaComplexType)Element.ElementSchemaType, Resolver).
                        GetArrayElement();

                return new ElementInfo(arrayElement, Owner);
            }

            return null;
        }

        public static bool GetIsSpecialArray(XmlSchemaElement element)
        {
            //lihtne array defineerimine kui max occcurs on suurem yhes , st et elemente võib reas mitu olla, aga kui 
            //element on array siis sisulisel tähendaks see topelT massiivi  aga praegu lihtsalt käsitleb seda yhe dimensioonilise massiivina

            return element.MaxOccurs > 1 && !element.IsArrayItemElement();

        }
        public ElementInfo(XmlSchemaElement element, SchemaObjectClassInfo owner) : base(owner)
        {
            if (!element.RefName.IsEmpty)
            {
                var minOccursTemp = element.MinOccurs;
                var maxOccursTemp = element.MaxOccurs;

                var refElement = element.RefName.ToSchemaElement(owner.Resolver);

                element = refElement;
                element.MinOccurs = minOccursTemp;
                element.MaxOccurs = maxOccursTemp;
            }
            Validate(element);

            Element = element;

            if (string.IsNullOrEmpty(FullName)) throw new ArgumentNullException("FullName");

            if (!element.IsArrayItemElement())
                AddAnonymousClasses(element, Owner, FullName);

        }

        private bool IsArrayElementAnonymos(XmlSchemaComplexType complexType, ISchemaObjectResolver resolver)
        {
            return new XmlSchemaArrayItemTypeWrapper(complexType, resolver).GetArrayElement().IsAnonymous();
        }
        private void AddAnonymousClasses(XmlSchemaElement element, SchemaObjectClassInfo owner, string propFullTypeName)
        {
            if (element.IsAnonymous())
            {
                if (element.IsSimpleType())
                    owner.AddAnonymousClass(SimpleTypeClassInfo.Load(element.SchemaType, owner.Resolver, propFullTypeName));

                if (element.IsComplexType())
                {
                    if (element.SchemaType.IsArray())
                        AddArrayAnonymousClass((XmlSchemaComplexType)element.SchemaType, owner, propFullTypeName);
                    else
                    {
                        var wr = new XmlSchemaComplexTypeWrapper((XmlSchemaComplexType)element.SchemaType, owner.Resolver);
                        ClassInfo ancls = SchemaObjectClassInfo.Load(wr, owner, propFullTypeName);
                        owner.AddAnonymousClass(ancls);
                    }
                }
            }
            else if (element.ElementSchemaType.IsArray())
                AddArrayAnonymousClass((XmlSchemaComplexType)element.ElementSchemaType, owner, propFullTypeName);

        }

        private void Validate(XmlSchemaElement element)
        {
            if (element.ElementSchemaType != null && element.IsAnonymous() && element.ElementSchemaType.QualifiedName == new XmlQualifiedName("anyType", "http://www.w3.org/2001/XMLSchema"))
                throw new Exception("Element : [" + element.Name + "] has type definition missing! ");
            
        }
        
        private void AddArrayAnonymousClass(XmlSchemaComplexType complexType, SchemaObjectClassInfo owner, string propFullTypeName)
        {
            var wr = new XmlSchemaArrayItemTypeWrapper(complexType, Resolver);
            if (wr.GetArrayElement().IsAnonymous())
            {
                var anonymoyType = wr.GetArrayElement().SchemaType;
                if (anonymoyType is XmlSchemaComplexType) owner.AddAnonymousClass(SchemaObjectClassInfo.Load((XmlSchemaComplexType)anonymoyType, owner, owner.Resolver, propFullTypeName));
                //anonyymne lihttyyp, mis asub elemendi sees , ehk siis userdefined ehk ,eraldi klass genereeritakse sell jaoks 
                if (anonymoyType is XmlSchemaSimpleType) owner.AddAnonymousClass(SimpleTypeClassInfo.Load(anonymoyType, owner.Resolver, propFullTypeName));
            }
        }

        private bool IsAnonymousArrayItem(XmlSchemaElement element)
        {
            if (element.IsAnonymous())
            {
                var complexType = element.Parent?.Parent as XmlSchemaComplexType;

                if (complexType != null)
                    return complexType.IsArray();

            }

            return false;
        }

        private string TryToResolveAnonymousTypeName(XmlSchemaElement el)
        {

            if (IsAnonymousArrayItem(el))
            {
                var element = el.Parent?.Parent?.Parent as XmlSchemaElement;

                if (!string.IsNullOrEmpty(element?.Name))
                    return element.Name.Pascalize() + "Type";
            }

            return el.Name.Pascalize() + "Type";
        }

        public bool IsXroadSpecific => Element.QualifiedName.Namespace == XmlSchemaTypeExtensions.XroadXsdSchema;
        
        private XmlSchema GetElementsXmlSchema(XmlSchemaElement xmlSchemaElement, XmlSchemaObject xmlSchemaObject)
        {
            
            var xmlSchema = xmlSchemaObject as XmlSchema;

            if (xmlSchema != null)
            {
                return xmlSchema;
            }

            if (xmlSchemaObject.Parent == null)
            {
                throw new Exception("Unable to locate elements " + xmlSchemaElement.Name + "(" + xmlSchemaElement.QualifiedName?.Namespace + ")" + " schema");
            }

            return GetElementsXmlSchema(xmlSchemaElement, xmlSchemaObject.Parent);
        }
    }
}
